package com.expand.learn.mock;

import org.junit.Test;
import org.mockito.ArgumentMatcher;
import org.mockito.InOrder;

import java.util.LinkedList;
import java.util.List;

import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.*;

// 静态导入会使代码更简洁

/**
 * @author zhanghaidong
 * @version v1.0
 * @description
 * @date 2020/3/13
 */
public class MockDemoTest {

    /**
     * Mockito使用verify去校验方法是否被调用
     * verify_behaviour
     */
    @Test
    public void verifyBehaviour() {
        // mock creation 创建mock对象
        List mockedList = mock(List.class);

        //using mock object 使用mock对象
        mockedList.add("one");
        mockedList.clear();

        //verification 验证
        verify(mockedList).add("one");
        verify(mockedList).clear();
    }

    /**
     * 如何做一些测试桩 (Stub)
     * when_thenReturn
     *
     * * 默认情况下，所有的函数都有返回值。mock函数默认返回的是null，一个空的集合或者一个被对象类型包装的内置类型，例如0、false对应的对象类型为Integer、Boolean；
     *
     * * 测试桩函数可以被覆写 : 例如常见的测试桩函数可以用于初始化夹具，但是测试函数能够覆写它。请注意，覆写测试桩函数是一种可能存在潜在问题的做法；
     *
     * * 一旦测试桩函数被调用，该函数将会一致返回固定的值；
     *
     * * 上一次调用测试桩函数有时候极为重要-当你调用一个函数很多次时，最后一次调用可能是你所感兴趣的。
     *
     * * 在测试桩中预设get(1) 跑出 RuntimeException 异常； 在@Test() 中设置 expected = RuntimeException.class ，说明该异常为测试用例预期结果。
     */
    @Test(expected = RuntimeException.class)
    public void whenThenReturn(){
        //You can mock concrete classes, not only interfaces
        // 你可以mock具体的类型,不仅只是接口
        LinkedList mockedList = mock(LinkedList.class);

        //stubbing
        // 测试桩
        when(mockedList.get(0)).thenReturn("first");
        when(mockedList.get(1)).thenThrow(new RuntimeException());

        //following prints "first"
        // 输出“first”
        System.out.println(mockedList.get(0));

        //following throws runtime exception
        // 抛出异常
        System.out.println(mockedList.get(1));

        //following prints "null" because get(999) was not stubbed
        // 因为get(999) 没有打桩，因此输出null
        System.out.println(mockedList.get(999));

        //Although it is possible to verify a stubbed invocation, usually it's just redundant
        //If your code cares what get(0) returns then something else breaks (often before even verify() gets executed).
        //If your code doesn't care what get(0) returns then it should not be stubbed. Not convinced? See here.
        // 验证get(0)被调用的次数
        verify(mockedList).get(0);
        // 验证get(0)是否被调用过1次
        verify(mockedList, times(1)).get(0);
    }

    /**
     * 参数匹配器 (matchers)
     * with_arguments
     */
    @Test
    public void withArguments(){

        LinkedList mockedList = mock(LinkedList.class);

        //stubbing using built-in anyInt() argument matcher
        // 使用内置的anyInt()参数匹配器
        when(mockedList.get(anyInt())).thenReturn("element");

        //following prints "element"
        // 输出element
        System.out.println(mockedList.get(1));

        //you can also verify using an argument matcher
        // 你也可以验证参数匹配器
        verify(mockedList).get(anyInt());

        //=========================自定义的参数匹配器=========================================


        //stubbing using custom matcher (let's say isValid() returns your own matcher implementation):
        // 使用自定义的参数匹配器( 在IsValid()类中返回你自己的匹配器实现 )
        when(mockedList.contains(argThat(new IsValid()))).thenReturn(true);

        assertTrue(mockedList.contains(1));
        assertTrue(!mockedList.contains(3));
    }

    private class IsValid implements ArgumentMatcher<Integer> {

        @Override
        public boolean matches(Integer argument) {
            return argument == 1;
        }
    }

    /**
     * 验证函数调用次数
     * verifying_number_of_invocations
     *
     * 1、verify函数默认验证的是执行了times(1)，也就是某个测试函数是否执行了1次.因此，times(1)通常被省略了。
     * 2、atLeastOnce() //验证至少调用一次
     * 3、atLeast(2) //验证至少调用2次
     * 4、atMost(5) //验证至多调用3次
     */
    @Test
    public void verifyingNumberOfInvocations(){
        LinkedList mockedList = mock(LinkedList.class);
        //using mock
        mockedList.add("once");

        mockedList.add("twice");
        mockedList.add("twice");

        mockedList.add("three times");
        mockedList.add("three times");
        mockedList.add("three times");

        //following two verifications work exactly the same - times(1) is used by default
        // 下面的两个验证函数效果一样,因为verify默认验证的就是times(1)
        verify(mockedList).add("once");
        verify(mockedList, times(1)).add("once");

        //exact number of invocations verification
        // 验证具体的执行次数
        verify(mockedList, times(2)).add("twice");
        verify(mockedList, times(3)).add("three times");

        //verification using never(). never() is an alias to times(0)
        // 使用never()进行验证,never相当于times(0)
        verify(mockedList, never()).add("never happened");

        //verification using atLeast()/atMost()
        // 使用atLeast()/atMost()
        verify(mockedList, atLeastOnce()).add("three times");
        verify(mockedList, atLeast(0)).add("five times");
        verify(mockedList, atMost(5)).add("three times");
    }

    /**
     * 验证执行顺序
     * 这里对验证的顺序有着严格的要求，
     * 如果将inOrder.verify(singleMock).add("was added first"); 和 inOrder.verify(singleMock).add("was added second");上下对调一下位置，则验证失败。
     */
    @Test
    public void verificationInOrder(){
        // A. Single mock whose methods must be invoked in a particular order
        // A. 验证mock一个对象的函数执行顺序
        List singleMock = mock(List.class);

        //using a single mock
        singleMock.add("was added first");
        singleMock.add("was added second");

        //create an inOrder verifier for a single mock
        // 为该mock对象创建一个inOrder对象
        InOrder inOrder = inOrder(singleMock);

        //following will make sure that add is first called with "was added first, then with "was added second"
        // 确保add函数首先执行的是add("was added first"),然后才是add("was added second")
        inOrder.verify(singleMock).add("was added first");
        inOrder.verify(singleMock).add("was added second");

        // B. Multiple mocks that must be used in a particular order
        // B .验证多个mock对象的函数执行顺序
        List firstMock = mock(List.class);
        List secondMock = mock(List.class);

        //using mocks
        firstMock.add("was called first");
        secondMock.add("was called second");

        //create inOrder object passing any mocks that need to be verified in order
        // 为这两个Mock对象创建inOrder对象
        InOrder inOrder2 = inOrder(firstMock, secondMock);

        //following will make sure that firstMock was called before secondMock
        // 验证它们的执行顺序
        inOrder2.verify(firstMock).add("was called first");
        inOrder2.verify(secondMock).add("was called second");
    }

    @Test(expected = RuntimeException.class)
    public void whenDoThrow() throws RuntimeException {

        LinkedList mockedList = mock(LinkedList.class);
        doThrow(new RuntimeException()).when(mockedList).clear();
        //following throws RuntimeException:
        // 下面的代码会抛出异常
        mockedList.clear();
    }

    @Test(expected = RuntimeException.class)
    public void whenDoAnswer() throws RuntimeException {

        LinkedList mockedList = mock(LinkedList.class);

        //Answer specifies an action that is executed and a return value that is returned when you interact with the mock.
        doAnswer(invocation -> {
            Object[] args = invocation.getArguments();
            Integer num = (Integer)args[0];
            if( num < 3 ){
                return "yes";
            } else {
                throw new RuntimeException();
            }
        }).when(mockedList).get(anyInt());

        // yes
        System.out.println(mockedList.get(1));
        // yes
        System.out.println(mockedList.get(2));
        // 抛出异常
        mockedList.get(5);
    }
}
